/* *****************************************************************************************************************************
 * (c) J@mBeL.net 2010-2015
 * Author: John Ambeliotis
 * Created: 24 Apr. 2010
 *
 * License:
 *  This file is part of Project jaNET.

    Project jaNET is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Project jaNET is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Project jaNET. If not, see <http://www.gnu.org/licenses/>. */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Linq;

namespace libJanet
{
    internal enum ScheduleStatus
    {
        Disable = 0,
        Enable = 1,
        Remove = 2,
        DisableAll = 3,
        EnableAll = 4,
        RemoveAll = 5
    }
	
    class Schedule
    {
        private static object _schedule_locker = new object();
    
        internal string Name { get; set; }
        internal string Date { get; set; }
        internal string Time  { get; set; }
        internal string Action { get; set; }
        private bool _status;
        public bool Status
        {
            get { return _status; }
            set {
                if (value != _status)
                {
                    _status = value;
                    SaveList();
                }
            }
        }
		
        internal Schedule()
        {
            //
        }
        
        internal Schedule(string sName, string sDate, string sTime, string sAction, bool bStatus)
        {
            this.Name = sName;
            this.Date = sDate;
            this.Time = sTime;
            this.Action = sAction;
            this.Status = bStatus;
        }

        private static ObservableCollection<Schedule> _ScheduleList = null;
        internal static ObservableCollection<Schedule> ScheduleList
        {
            get
            {
                if( _ScheduleList == null )
                {
                    _ScheduleList = new ObservableCollection<Schedule>();
                    _ScheduleList.CollectionChanged += ScheduleList_Changed;
                }
                return _ScheduleList;
            }
        }
        
        private static void ScheduleList_Changed(object sender, NotifyCollectionChangedEventArgs e)
        {
            SaveList();
        }
		
        /// <summary>
        /// initialize scheduler.
        /// </summary>
        internal static void Init()
        {
            new Thread(() => {
                string schedulerFilename = ".scheduler";
                
                if (File.Exists(Methods.getApplicationPath() + schedulerFilename))
                {
                    if (ScheduleList.Count > 0)
                        ScheduleList.Clear();
                    FileSystem.Settings scheduleSettings = new FileSystem.Settings();
                    IList<String> Schedules = scheduleSettings.LoadSettings(schedulerFilename);
                    foreach (string schedule in Schedules)
                        if (schedule != string.Empty)
                            Judoers.JudoParser("judo schedule add " + schedule);
                }
            }).Start();
        }

        private static void SaveList()
        {
            string schedules = string.Empty;
            string schedulerPath = ".scheduler";

            FileSystem.Settings scheduleSettings = new FileSystem.Settings();
            
            foreach (Schedule schedule in ScheduleList)
                schedules += schedule.Name + " " + schedule.Date + " " + schedule.Time + " '" + schedule.Action + "' " + schedule.Status + "\r\n";
            
            scheduleSettings.SaveSettings(schedulerPath, schedules);
        }
		
        internal static string Add(Schedule ss)
        {
            return Add(ss, 1000);
        }
		
        internal static string Add(Schedule ss, int Interval)
        {
            try
            {
                if (!ScheduleList.Contains(ss))
                    ScheduleList.Add(ss);

                lock (_schedule_locker)
                    Task.Factory.StartNew(() => ScheduleListener(ss, Interval));
                
                return "Schedule " + ss.Name + " added";
            }
            catch { return "Failed to add " + ss.Name + " schedule"; }
        }

        private static void ScheduleListener(Schedule oSchedule, int Interval)
        {
            bool _done = false;
			
            IList<DayOfWeek> WorkDays = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
            IList<DayOfWeek> Weekend = new DayOfWeek[] { DayOfWeek.Saturday, DayOfWeek.Sunday };

            lock (_schedule_locker)
            {
                while (oSchedule.Status)
                {
                    if ((oSchedule.Date.ToUpper() == "REPEAT" || oSchedule.Date.ToUpper() == "INTERVAL" || oSchedule.Date.ToUpper() == "TIMER") || // repeated (ms)
                        (oSchedule.Date.ToUpper() == "DAILY" || oSchedule.Date.ToUpper() == "EVERYDAY") && oSchedule.Time == Methods.getTime24() || // every day
                         oSchedule.Date.ToUpper() == "WORKDAYS" && oSchedule.Time == Methods.getTime24() && WorkDays.Contains(DateTime.Now.DayOfWeek) || // workdays
                         oSchedule.Date.ToUpper() == "WEEKEND" && oSchedule.Time == Methods.getTime24() && Weekend.Contains(DateTime.Now.DayOfWeek) || // weekends
                         oSchedule.Date.ToUpper().Contains(Methods.getDay().ToUpper()) && oSchedule.Time == Methods.getTime24() || // specific day
                         oSchedule.Date == Methods.getCalendarDate() && oSchedule.Time == Methods.getTime24()) // specific date - only once, then deleted
                    {
                        if (!_done)
                        {
                            if (Methods.getInstructionSet(oSchedule.Action).Count > 0)
                                Parser.Parse(oSchedule.Action);
                            else
                                Parser.SayText(oSchedule.Action);
                        
                            if (oSchedule.Date.ToUpper() != "REPEAT" && oSchedule.Date.ToUpper() != "INTERVAL" && oSchedule.Date.ToUpper() != "TIMER")
                                _done = true;
                        
                            if (oSchedule.Date.ToUpper() != "REPEAT" && oSchedule.Date.ToUpper() != "INTERVAL" && oSchedule.Date.ToUpper() != "TIMER" &&
                                oSchedule.Date.ToUpper() != "DAILY" && oSchedule.Date.ToUpper() != "EVERYDAY" && oSchedule.Date.ToUpper() != "WEEKEND" &&
                                oSchedule.Date.ToUpper() != "WORKDAYS" && !oSchedule.Date.ToUpper().Contains(Methods.getDay().ToUpper()))
                                    ChangeStatus(oSchedule.Name, ScheduleStatus.Remove);
                        }
                    }
                    else
                        _done = false;

                    Monitor.Wait(_schedule_locker, Interval);
                }
            }
        }
		
        internal static string ChangeStatus(ScheduleStatus Status)
        {
            return ChangeStatus(string.Empty, Status);
        }
        
        internal static string ChangeStatus(string scheduleName, ScheduleStatus Status)
        {
            try
            {
                if (Status == ScheduleStatus.Remove || Status == ScheduleStatus.RemoveAll) // Remove - RemoveAll
                    for (int i = ScheduleList.Count - 1; i >= 0 ; i--)
                    {
                        if (ScheduleList[i].Name == scheduleName || Status == ScheduleStatus.RemoveAll)
                        {
                            ScheduleList[i].Status = false;
                            ScheduleList.RemoveAt(i);
                        }
                    }
                else if (Status == ScheduleStatus.Enable || Status == ScheduleStatus.Disable) // Enable - Disable
                    foreach (Schedule s in ScheduleList.Where(s => s.Name ==  scheduleName).Where(s => s.Status != Convert.ToBoolean(Status)))
                    {
                        s.Status = Convert.ToBoolean(Status);
                        if (s.Status)
                            if (s.Time.Contains(":"))
                                Add(s);
                        else
                            Add(s, Convert.ToInt32(s.Time));
                    }
                else if (Status == ScheduleStatus.EnableAll) // EnableAll
                    foreach (Schedule s in ScheduleList.Where(s => s.Status == false))
                    {
                        s.Status = true;
                        Add(s);
                    }
                else if (Status == ScheduleStatus.DisableAll) // DisableAll
                    foreach (Schedule s in ScheduleList.Where(s => s.Status == true))
                        s.Status = false;

                lock (_schedule_locker)
                    Monitor.PulseAll(_schedule_locker);

                return "Scheduler updated [" + scheduleName + ":" + Status + "]";
            }
            catch { return "Failed to " + Status + " schedule " + scheduleName; }
        }
    }
}